Kalıtıma Giriş
Java'da kalıtım ve kompozisyon, nesne yönelimli programlama prensiplerinden ikisidir ve sınıfların birbirine olan ilişkisini belirlemek için kullanılır. Her birinin farklı avantajları ve kullanım durumları vardır.
Kalıtım, bir sınıfın, başka bir sınıfın özelliklerini ve davranışlarını miras alarak genişletmesini sağlar. Bu, kodun yeniden kullanılabilirliğini artırır, kod tekrarını önler ve sınıflar arasındaki bağımlılıkları azaltır. Kalıtımın bir diğer faydası, bir sınıfın, miras aldığı sınıfların yöntemlerini ve özelliklerini kullanarak, karmaşık davranışları gerçekleştirebilmesidir. Ancak kalıtım kullanırken, alt sınıfın üst sınıftan miras aldığı özellikleri ve davranışları kullanması için üst sınıfın iyi tasarlanmış ve stabil olması gerekir.
Kompozisyon ise, bir sınıfın, diğer sınıfları birleştirerek yeni bir sınıf oluşturmasını sağlar. Bu, kodun modülerliğini artırır, sınıflar arasındaki bağımlılıkları azaltır ve karmaşık nesnelerin oluşturulmasını sağlar. Kompozisyon kullanırken, bir sınıfın diğer sınıflara bağımlı olması ve onları kullanarak karmaşık davranışları gerçekleştirmesi gerekir.
Genel olarak, kalıtım, nesneler arasında bir "is-a" ilişkisi varsa kullanılırken, kompozisyon, nesneler arasında bir "has-a" ilişkisi varsa kullanılır. Örneğin, bir Araba sınıfı bir Motor sınıfından türetilmiş olabilir, çünkü bir arabaya bir motorun ait olması "is-a" ilişkisini gösterir. Öte yandan, bir Araba sınıfı bir Lastik sınıfı içerebilir, çünkü bir arabada lastiklerin bulunması "has-a" ilişkisini gösterir.
Kompozisyon
Java'da kompozisyon, bir sınıfın, diğer sınıfların örneklerini kullanarak bir nesne oluşturmasını sağlayan bir nesne yönelimli programlama prensibidir. Bu, bir sınıfın karmaşık özelliklerini ve davranışlarını, diğer sınıflara bölerek daha modüler ve okunaklı bir kod oluşturmasına yardımcı olur.
Örnek olarak, bir "Araba" sınıfı oluşturmak istediğimizi düşünelim. Araba, bir motor, bir şanzıman ve bir dizi lastik gibi birçok bileşen içerir. Bu bileşenlerin hepsi ayrı sınıflarda modellenebilir ve Araba sınıfı bu bileşenleri kullanarak oluşturulabilir.
Bunu gerçekleştirmek için, Araba sınıfında diğer sınıfların örneklerini oluşturabilir ve onları kullanabiliriz. Aşağıdaki örnek, Araba sınıfının, bir Motor ve bir Şanzıman sınıfı örneği içerdiğini ve ayrıca dört adet Lastik örneğine sahip olduğunu göstermektedir:
public class Araba {
private Motor motor;
private Şanzıman şanzıman;
private Lastik[] lastikler;
public Araba() {
motor = new Motor();
şanzıman = new Şanzıman();
lastikler = new Lastik[4];
for(int i=0; i<4; i++) {
lastikler[i] = new Lastik();
}
}
// diğer metodlar
}
public class Motor {
// Motor sınıfı özellikleri ve davranışları
}
public class Şanzıman {
// Şanzıman sınıfı özellikleri ve davranışları
}
public class Lastik {
// Lastik sınıfı özellikleri ve davranışları
}
Bu örnekte, Araba sınıfının motor, şanzıman ve lastikler adında üç özelliği vardır. Araba sınıfının yapıcısı, motor ve şanzıman örneklerini oluştururken, lastik örnekleri için bir dizi oluşturur ve her bir öğe için ayrı ayrı Lastik sınıfı örneği oluşturur.
Bu şekilde, Araba sınıfı, Motor, Şanzıman ve Lastik sınıflarını kullanarak oluşturulur ve Araba nesnesi, bu bileşenlerin tüm özelliklerini ve davranışlarını içerir. Bu sayede, Araba sınıfının kodu daha modüler ve okunaklı hale gelir, ayrıca Araba sınıfının değişikliklerinden diğer bileşenler etkilenmez.
Kalıtım
Java'da kalıtım (inheritance), bir sınıfın başka bir sınıfın özelliklerini ve davranışlarını miras almasıdır. Kalıtım sayesinde bir sınıf, var olan bir sınıftan tüm özelliklerini ve davranışlarını alarak yeni bir sınıf oluşturabilir. Kalıtım, Java'da nesne yönelimli programlama (Object-Oriented Programming) temel prensiplerinden biridir ve programlamada kod tekrarını azaltarak kodun yeniden kullanılabilirliğini arttırır.
Kalıtımın kullanımı, "extends" anahtar kelimesi ile gösterilir. Örneğin, bir "Person" sınıfı oluşturabilir ve bu sınıfta insanlar için genel özellikler ve davranışlar tanımlayabiliriz. Daha sonra, bu sınıfı temel alarak "Student" ve "Teacher" sınıflarını oluşturabiliriz. Bu sınıflar, "Person" sınıfının tüm özelliklerini ve davranışlarını kalıtım yoluyla almış olurlar.
Aşağıda örnek bir kod gösterimi bulunmaktadır:
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("Person is eating.");
}
public void sleep() {
System.out.println("Person is sleeping.");
}
}
class Student extends Person {
private int studentId;
public Student(String name, int age, int studentId) {
super(name, age);
this.studentId = studentId;
}
public void study() {
System.out.println("Student is studying.");
}
}
class Teacher extends Person {
private String subject;
public Teacher(String name, int age, String subject) {
super(name, age);
this.subject = subject;
}
public void teach() {
System.out.println("Teacher is teaching " + this.subject + ".");
}
}
Yukarıdaki örnekte, "Person" sınıfı insanların temel özelliklerini ve davranışlarını tanımlar. "Student" sınıfı, "Person" sınıfından kalıtım yoluyla tüm özelliklerini ve davranışlarını almıştır ve ek olarak "studentId" özelliğini de içermektedir. "Teacher" sınıfı da aynı şekilde "Person" sınıfından kalıtım yoluyla tüm özelliklerini ve davranışlarını almıştır ve ek olarak "subject" özelliğini de içermektedir.
Bu örnekte, "Student" ve "Teacher" sınıfları, "Person" sınıfından kalıtım yoluyla tüm özelliklerini ve davranışlarını almış ve kendi özelliklerini ve davranışlarını da eklemişlerdir. Bu sayede, kod tekrarından kaçınılmış ve yeniden kullanılabilir bir kod yazılmıştır.
Overriding
Overriding, Java programlama dilinde bir alt sınıfın üst sınıfın metodunu aynı isim ve imza ile yeniden tanımlamasıdır. Bu sayede alt sınıf, üst sınıfın metodunu kendi ihtiyacına göre değiştirebilir ve aynı isim ve imza ile kullanabilir.
Kalıtım kullanıldığında, bir alt sınıfın üst sınıfın metodlarını kullanabileceği gibi, bu metodları override ederek kendine özgü metodlar da tanımlayabilir. Bu işlem, bir alt sınıfın üst sınıfın metodunu, aynı isim ve imza ile yeniden tanımlaması ile gerçekleştirilir.
Aşağıda, bir örnek kod gösterimi verilmiştir:
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.makeSound(); // The animal makes a sound
Cat cat = new Cat();
cat.makeSound(); // Meow
}
}
Yukarıdaki örnekte, "Animal" sınıfı "makeSound" metodunu tanımlamıştır. "Cat" sınıfı, "Animal" sınıfından kalıtım yoluyla tüm özelliklerini ve davranışlarını almıştır. Ancak, "Cat" sınıfı "makeSound" metodunu override ederek, kendi özel "Meow" metodu tanımlamıştır. Sonuç olarak, "Animal" sınıfından bir nesne oluşturulduğunda "The animal makes a sound" çıktısı verilirken, "Cat" sınıfından bir nesne oluşturulduğunda "Meow" çıktısı verilir.
Overriding, kalıtım kullanarak sınıflar arasında birbirlerinden farklı özellikler ve davranışlar eklemek için önemli bir araçtır. Bu sayede, sınıflar birbirine benzer olsa da, özellikle alt sınıflarda farklılıklar oluşturulabilir ve programın modülerliği ve bakımı kolaylaştırılabilir.
Overloading
Overloading, aynı isimli fakat farklı parametrelerle birlikte farklı şekillerde kullanılan metodların tanımlanmasıdır. Bu sayede, aynı işlevi farklı parametrelerle çağırarak kodun daha modüler ve kolay anlaşılabilir olmasını sağlar.
Java'da kalıtım konusu ile düşünürsek, bir sınıfın başka bir sınıftan kalıtım alması durumunda, kalıtım aldığı sınıfın metodlarına erişebilir ve bu metodları istediği şekilde kullanabilir. Ancak, kalıtım alan sınıf aynı isimli bir metodunu tanımlarsa, bu durumda metot overloading özelliği devreye girer.
Örneğin, bir Hayvan sınıfı tanımlayalım ve bu sınıfın konuşma özelliği olsun. Hayvan sınıfı aşağıdaki şekilde tanımlanabilir:
public class Hayvan {
public void konus() {
System.out.println("Hayvan konuşuyor.");
}
}
Ardından, bu sınıftan kalıtım alan bir Köpek sınıfı tanımlayalım ve Köpek sınıfı da konuşma özelliğine sahip olsun. Ancak, Köpek sınıfının konuşma özelliği farklı bir şekilde çalışabilir. Böylece, Köpek sınıfı Hayvan sınıfının konuşma metodunu overloading yaparak kendi metodunu tanımlayabilir.
public class Kopek extends Hayvan {
@Override
public void konus() {
System.out.println("Hav! Hav!");
}
public void konus(String mesaj) {
System.out.println(mesaj);
}
}
Bu örnekte, Kopek sınıfı, Hayvan sınıfından kalıtım alarak konuşma özelliğini miras almıştır. Ancak, Kopek sınıfı Hayvan sınıfının konuşma metodunu overloading yaparak kendi konuşma metodunu tanımlamıştır. Bu sayede, Köpek sınıfı Hayvan sınıfından kalıtım aldığı halde, kendi metodunu kullanarak farklı bir işlev görür.
Upcasting
Java'da upcasting, bir alt sınıf nesnesinin bir üst sınıf referansı ile işaret edilmesi anlamına gelir. Bu, bir nesnenin, o nesneyi oluşturan sınıfın yanı sıra, üst sınıfların tüm referanslarını da alabileceği anlamına gelir. Upcasting genellikle, bir sınıf hiyerarşisindeki bir nesnenin daha genel bir şekilde işlem yapılması gerektiğinde kullanılır.
Örneğin, bir Hayvan sınıfı ve bu sınıftan türetilen bir Köpek sınıfı olsun. Hayvan sınıfı, tüm hayvanların sahip olabileceği ortak özellikleri içerirken, Köpek sınıfı sadece köpeklerin sahip olduğu özellikleri içerir. Ancak, Köpek sınıfı Hayvan sınıfından türetilmiştir ve Hayvan sınıfının tüm özelliklerini miras alır.
Aşağıdaki örnek, bir Köpek nesnesinin Hayvan sınıfı referansı ile işaret edilmesini gösterir:
Hayvan hayvan = new Kopek();
Bu örnekte, bir Köpek nesnesi oluşturulmuş ve Hayvan sınıfı referansı ile işaret edilmiştir. Bu, upcasting işlemidir. Yani, bir alt sınıf nesnesi olan Kopek nesnesi, üst sınıf referansı olan Hayvan sınıfı referansı ile işaret edilmiştir. Bu sayede, Köpek nesnesinin tüm özellikleri kullanılamasa da, Hayvan sınıfının sahip olduğu tüm özelliklere erişilebilir.
Örneğin, Hayvan sınıfına bir konuşma metodu ekleyelim:
public class Hayvan {
public void konus() {
System.out.println("Hayvan konuşuyor.");
}
}
Ardından, Köpek sınıfına da bir konuşma metodu ekleyelim:
public class Kopek extends Hayvan {
@Override
public void konus() {
System.out.println("Hav! Hav!");
}
}
Bu durumda, aşağıdaki kod bloğu çalıştırıldığında "Hav! Hav!" yazısı ekrana yazdırılacaktır:
Hayvan hayvan = new Kopek();
hayvan.konus();
Burada, Köpek nesnesi, Hayvan sınıfı referansı ile işaret edildiği için, Hayvan sınıfının konuşma metodu çağrıldı. Ancak, Köpek sınıfı Hayvan sınıfının konuşma metodunu override ettiği için, Köpek sınıfının konuşma metodu çalıştı. Bu sayede, upcasting işlemi sayesinde, Köpek nesnesi Hayvan sınıfı referansı ile işaret edilerek, Hayvan sınıfının konuşma metodu kullanılarak çalıştırıldı.
Downcasting
Java'da downcasting, bir üst sınıf referansı ile işaret edilen bir nesnenin, alt sınıf referansı ile işaret edilmesi işlemidir. Bu, upcasting işleminin tam tersidir ve bazen bir üst sınıf referansı ile işaret edilen nesneyi, alt sınıfın özel bir özelliğine erişmek için kullanılır.
Downcasting, önce upcasting işlemi yapılması gerektiği için, öncelikle üst sınıf nesnesinin bir alt sınıf nesnesine dönüştürülmesi gereklidir. Bu işlem, "instanceof" anahtar kelimesi ile kontrol edilerek yapılır. "instanceof" anahtar kelimesi, bir nesnenin bir sınıfın örneği olup olmadığını kontrol eder. Eğer bir nesne belirtilen sınıfın örneği ise, true değeri döndürür, aksi takdirde false değeri döndürür.
Aşağıdaki örnekte, bir Hayvan nesnesi bir Köpek nesnesine dönüştürülmek istendiğinde, "instanceof" anahtar kelimesi kullanılarak kontrol edilir:
Hayvan hayvan = new Kopek();
if (hayvan instanceof Kopek) {
Kopek kopek = (Kopek) hayvan;
kopek.havla();
}
Bu örnekte, öncelikle Hayvan sınıfı referansı ile bir Köpek nesnesi oluşturulmuştur. Daha sonra, "instanceof" anahtar kelimesi kullanılarak, hayvan nesnesinin bir Kopek nesnesi olup olmadığı kontrol edilmiştir. Eğer hayvan nesnesi bir Kopek nesnesi ise, "hayvan" nesnesi "Kopek" sınıfına downcast edilmiştir. Bu sayede, Kopek sınıfına özgü olan "havla" metoduna erişilebilmiştir.
Downcasting işlemi, yanlış bir sınıf referansı ile yapılırsa, "ClassCastException" hatası oluşabilir. Bu hatanın önüne geçmek için, öncelikle "instanceof" anahtar kelimesi ile nesnenin doğru bir şekilde downcast edilebileceği kontrol edilmelidir.
Örneğin, aşağıdaki örnekte, Hayvan nesnesi, Kedi sınıfına downcast edilmeye çalışılıyor ve bu durum "ClassCastException" hatasına neden oluyor:
Hayvan hayvan = new Kopek();
if (hayvan instanceof Kedi) {
Kedi kedi = (Kedi) hayvan;
kedi.miyavla();
}
Bu örnekte, "hayvan" nesnesi bir Köpek nesnesi olduğu için, "instanceof" anahtar kelimesi Kedi sınıfı referansı ile karşılaştırıldığında false değerini döndürür.
Final Özelliği
Java'da "final" anahtar kelimesi, değişmez bir özellik tanımlamak için kullanılır. Bu anahtar kelime, değişken, metod ve sınıf tanımlamalarında kullanılabilir.
- Değişkenlerde final anahtar kelimesi
Bir değişkenin "final" olarak tanımlanması, o değişkenin bir kez değer atanmasına izin verir ve daha sonra değeri değiştirilemez. Final değişkenler genellikle sabitler, yapılandırma bilgileri ve program içinde çok kez kullanılacak değerler gibi değişmez değerlerin tanımlanması için kullanılır. Final değişkenlerin değerleri, ilk atamadan sonra hiçbir zaman değiştirilemez.
final int PI = 3.14;
- Metodlarda final anahtar kelimesi
Final anahtar kelimesi, bir metodun alt sınıflar tarafından yeniden tanımlanmasını engellemek için kullanılır. Final metodlar, alt sınıflarda yeniden tanımlanamaz. Bu, güvenliğin ve kodun daha kolay anlaşılabilirliğinin sağlanması için önemlidir.
public final void setYas(int yas) {
this.yas = yas;
}
- Sınıflarda final anahtar kelimesi
Final anahtar kelimesi, bir sınıfın alt sınıflarının oluşturulmasını engellemek için kullanılabilir. Final sınıflar, alt sınıfları olmayan sınıflardır.
public final class Circle {
//...
}
Final anahtar kelimesi, Java kodunun güvenliğini artırır ve hataları önler. Final değişkenler, Java kodunun daha anlaşılır hale gelmesine yardımcı olur. Final metodlar ve sınıflar, Java kodunun daha modüler ve ölçeklenebilir olmasını sağlar. Final anahtar kelimesi, Java programcıları tarafından yaygın olarak kullanılır ve Java kodunun okunabilirliğini, bakımını ve güvenliğini artırır.